package com.zpark.evaluate;

import com.zpark.entity.EvaluateData;
import com.zpark.entity.EvaluateReport;
import com.zpark.entity.HistoryData;
import com.zpark.entity.RiskFactor;

import java.util.*;

/**
 * 密码相似度评估
 * 通过余弦相似性完成
 */
public class SimilarityEvaluate extends Evaluate {

    private double threshold;

    public SimilarityEvaluate(double threshold) {
        super(RiskFactor.SIMILARITY);
        this.threshold = threshold;
    }

    @Override
    public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {

        boolean isRisk = doEval(evaluateData.getOrdernessPassword(),historyData.getHistorypasswords());
        evaluateReport.signReport(getRiskFactor(),isRisk);

        //继续
        evaluateChain.doChain(evaluateData, historyData, evaluateReport);
    }

    private boolean doEval(String ordernessPassword, Set<String> historypasswords) {

        //1.如果是第一次登录，没有风险
        if(historypasswords==null || historypasswords.size()==0){
            return false;
        }

        //把历次留存的密码以及现在要评估的密码，转换成一个集合，集合中存储的是不重复的字符
        Set<Character> set = new HashSet<>();
        //历次留存的密码中的不重复的字符
        for (String historypassword : historypasswords) {
            for (int i=0;i<historypassword.length();i++){
                set.add(historypassword.charAt(i));
            }
        }

        //当前要评估的密码中的字符，也加入到set集合中
        for (int i=0;i<ordernessPassword.length();i++){
            set.add(ordernessPassword.charAt(i));
        }

        //把set集合转换成List集合
        List<Character> wordBag=new ArrayList<>();
        for (Character character : set) {
            wordBag.add(character);
        }

        //把评估密码转换成向量
        Integer[] evaluateEvctor = converStringToVector(wordBag, ordernessPassword);

        //所有留存的密码都转换成向量
        List<Integer[]> historyEvctors=new ArrayList<>();
        for (String historypassword : historypasswords) {
            historyEvctors.add(converStringToVector(wordBag,historypassword));
        }


        //用评估向量和留存的向量计算相似度，如果有一个相似度大于规定值，就认定没有风险
        for (Integer[] historyEvctor : historyEvctors) {
            double cos = cos(historyEvctor, evaluateEvctor);
            if(cos>threshold){
                return false;
            }
        }

        return true;//该项评估有风险
    }

    /**
     * 把字符串转换成向量
     * @param wordBag 历次登录留存的密码以及当前要评估的密码合并起来形成的所有字符
     * @param password 要转换成向量的字符串
     * @return
     */
    private Integer[] converStringToVector(List<Character> wordBag, String password){
        //创建一个空的特征向量
        Integer[] vector = new Integer[wordBag.size()];
        //创建一个Map用来统计密码的相同字符数量
        HashMap<Character, Integer> charMap = new HashMap<>();
        //统计字符数量
        for (Character c : password.toCharArray()) {
            Integer count = 1;
            if (charMap.containsKey(c)){
                count+=charMap.get(c);
            }
            charMap.put(c,count);
        }
        //通过密码的字符数量，对比词条库生成特征向量
        for (int i = 0; i < wordBag.size(); i++) {
            Character c = wordBag.get(i);
            vector[i] = charMap.containsKey(c)?charMap.get(c):0;
        }
        return vector;
    }

    /**
     * 计算两个向量间的余弦值
     * 通过数学公式完成计算
     *
     * @param a
     * @param b
     * @return
     */
    private double cos(Integer[] a,Integer[] b){
        //计算分子
        double sum = 0.0;
        for (int i = 0; i < a.length; i++) {
            sum+=a[i]*b[i];
        }

        //计算分母
        double sumA=0.0;
        for (Integer integer : a) {
            sumA+=integer*integer;
        }
        double sumB=0.0;
        for (Integer integer : b) {
            sumB+=integer*integer;
        }
        double fenMu = Math.sqrt(sumA)*Math.sqrt(sumB);


        return sum/fenMu;
    }
}
